/*
 *  strata.c : Intel Strata Flash operations
 *
 *  Copyright (c) 2003, Intel Corporation (yu.tang@intel.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 */

#ifdef HAVE_CONFIG_H
# include <blob/config.h>
#endif

#include <blob/arch.h>
#include <blob/serial.h>
#include <blob/util.h>

#include <strata.h>

#define REGION_SEL(blkadr, chip) ({ \
	int __i; \
	struct strata_flash_region *__r = NULL; \
	for (__i=0; __i<chip->region_num; __i++) { \
		u32 __start, __end; \
		__start = chip->regions[__i].offset + chip->base; \
		__end = __start + chip->regions[__i].block_num * chip->regions[__i].block_size; \
		if ( (blkadr >= __start) && (blkadr < __end)) { \
			__r = chip->regions + __i; \
			break; \
		} \
	} \
	__r; \
})

/* J3 */
#define J_32  { {0,  32, 64*1024}, }
#define J_64  { {0,  64, 64*1024}, }
#define J_128 { {0, 128, 64*1024}, }
/* K18/K3 */
#define K_64   { {0,  64, 64*1024}, }
#define K_128  { {0, 128, 64*1024}, }
#define K_256  { {0, 256, 64*1024}, }
/* L18/L3, top/bottom */
#define L_top_64      { {0,  63, 64 * 1024}, {0x3F0000,  4, 16 * 1024}, }
#define L_top_128     { {0, 127, 64 * 1024}, {0x7F0000,  4, 16 * 1024}, }
#define L_top_256     { {0, 255, 64 * 1024}, {0xFF0000,  4, 16 * 1024}, }
#define L_bottom_64   { {0,   4, 16 * 1024}, {0x010000, 63, 64 * 1024}, }
#define L_bottom_128  { {0,   4, 16 * 1024}, {0x010000,127, 64 * 1024}, }
#define L_bottom_256  { {0,   4, 16 * 1024}, {0x010000,255, 64 * 1024}, }

static struct strata_flash_descriptor chips_table[]=
{
	/* base, buswidth, id, type, region_num, regions */
	{ 0, 0, 0x0016, 16, STRATA_J3,  1, J_32  }, /* J3, 32Mbit */
	{ 0, 0, 0x0017, 16, STRATA_J3,  1, J_64  }, /* J3, 64Mbit */ 
	{ 0, 0, 0x0018, 16, STRATA_J3,  1, J_128 }, /* J3, 128bit */
	{ 0, 0, 0x8801, 32, STRATA_K3,  1, K_64 },  /* K3   64Mbit */
	{ 0, 0, 0x8802, 32, STRATA_K3,  1, K_128}, /* K3  128Mbit */
	{ 0, 0, 0x8803, 32, STRATA_K3,  1, K_256}, /* K3  256Mbit */
	{ 0, 0, 0x8805, 32, STRATA_K18, 1, K_64},  /* K18  64Mbit */
	{ 0, 0, 0x8806, 32, STRATA_K18, 1, K_128}, /* K18 128Mbit */
	{ 0, 0, 0x8807, 32, STRATA_K18, 1, K_256}, /* K18 256Mbit */
	{ 0, 0, 0x880B, 32, STRATA_L18, 2, L_top_64},  /* L18   64Mbit, Top */
	{ 0, 0, 0x880C, 32, STRATA_L18, 2, L_top_128}, /* L18  128Mbit, Top */
	{ 0, 0, 0x880D, 32, STRATA_L18, 2, L_top_256}, /* L18  256Mbit, Top */
	{ 0, 0, 0x880E, 32, STRATA_L18, 2, L_bottom_64}, /* L18  64Mbit, Bottom */
	{ 0, 0, 0x880F, 32, STRATA_L18, 2, L_bottom_128}, /* L18 128Mbit, Bottom */
	{ 0, 0, 0x8810, 32, STRATA_L18, 2, L_bottom_256}, /* L18 256Mbit, Bottom */
	{ 0, 0, 0x8811, 32, STRATA_L3,  2, L_top_64}, /* L3   64Mbit, Top */
	{ 0, 0, 0x8812, 32, STRATA_L3,  2, L_top_128}, /* L3  128Mbit, Top */
	{ 0, 0, 0x8813, 32, STRATA_L3,  2, L_top_256}, /* L3  256Mbit, Top */
	{ 0, 0, 0x8814, 32, STRATA_L3,  2, L_bottom_64}, /* L3  64Mbit, Bottom */
	{ 0, 0, 0x8815, 32, STRATA_L3,  2, L_bottom_128}, /* L3 128Mbit, Bottom */
	{ 0, 0, 0x8816, 32, STRATA_L3,  2, L_bottom_256}  /* L3 256Mbit, Bottom */
};

static int unlock_block_32bit(u32 addr)
{
	volatile u32 *blkadr= (volatile u32*)addr;
	u32 status;

	/* clear status */
	*blkadr = 0x00500050;

	*blkadr = 0x00600060;
	*blkadr = 0x00d000d0;

	do {
		status = *blkadr;
	}while( (status & 0x00800080) != 0x00800080);

	
	if( status != 0x00800080) {
		printf("%s: error code = 0x%08x\n", __FUNCTION__, (int)status);
		return 1;
	}

	*blkadr = 0x00ff00ff;

	return 0;
}

static int erase_block_32bit(u32 addr)
{
	volatile u32 *blkadr = (volatile u32*) addr;
	u32 status;

	/* clear status */
	*blkadr = 0x00500050;

	*blkadr = 0x00200020;
	*blkadr = 0x00d000d0;

	/* BUSY? */
	do {
		status = *blkadr;
	}while( (status & 0x00800080) != 0x00800080 );

	/* erase error? */
	if( (status & 0x00200020) == 0x00200020) {
		printf("%s: error code = 0x%08x\n", __FUNCTION__, (int)status);
		return 1;
	}

	*blkadr = 0x00ff00ff;

	return 0;
}

static int do_flash_probe_32bit(struct strata_flash_descriptor *chip)
{
	volatile u32 *addr;
	u32 code, id;

	if (!chip) return -1;

	addr = (volatile u32 *)chip->base;

	*addr = 0x00980098;
	code = *addr;
	id = *(addr+1);
	*addr = 0x00ff00ff;

	chip->type = STRATA_UNKNOWN;
	chip->id = id;

	code &= 0xffff;
	id &= 0xffff;

	if(code == 0x89) {
		int i,j; 
		struct strata_flash_descriptor *c;

		for (i=0; i< (sizeof(chips_table)/sizeof(struct strata_flash_descriptor)); i++) {
			c = chips_table + i;
			if ( id == c->id) {
				chip->type = c->type;
				chip->cache_size = c->cache_size * 4;
				chip->region_num = c->region_num;

				for (j=0; j < c->region_num; j++) {
					chip->regions[j].offset = c->regions[j].offset * 4;
					chip->regions[j].block_num = c->regions[j].block_num;
					chip->regions[j].block_size = c->regions[j].block_size * 4;
				}

				break;
			}
		}
	}
				
	return 0;
}

static int do_flash_erase_32bit(struct strata_flash_descriptor *chip, u32 dest, u32 len)
{
	int blocks, ret;
	u32 blkadr;
	struct strata_flash_region *r;

	if (!chip) return -1;

	/* unlocking */
	blocks  = 0;
	blkadr  = dest;
	for(;;) {
		/* J3 : unlock one for all, K3/K18/L3/L18 : unlock for each block */
		if (chip->type == STRATA_J3) {
			if (!blocks) unlock_block_32bit(blkadr);
		}
		else
			unlock_block_32bit(blkadr);

		r = REGION_SEL(blkadr, chip);
		if (!r) return -1;
		blkadr += r->block_size;

		blocks ++;

		if (blkadr >= (dest+len)) break;
	}

	printf("Erasing %d blocks at 0x%08x:", blocks, (int)dest);

	/* erasing */
	blkadr = dest;
	for(;;) {
		ret = erase_block_32bit(blkadr);
		if (ret) return ret;

		r = REGION_SEL(blkadr, chip);
		if (!r)  return -1;
		blkadr += r->block_size;

		/* show progress */
		printf(".");
		if (blkadr >= (dest+len)) break;
	}

	printf("done\n");

	return 0;
}

static int do_write_word_32bit(struct strata_flash_descriptor *chip, u32 dest, u32 data) 
{
	volatile u32 *addr = (volatile u32*) dest;
	u32 status;

	if (!chip) return -1;

	/* clear status */
	*addr = 0x00500050;

	/* program setup */
	*addr= 0x00400040;

	/* data */
	*addr = data;

	/* read status */
	*addr = 0x00700070;

	do{
		status = *addr;
	}while( (status & 0x00800080) != 0x00800080 );

	/* program error? */
	if( (status & 0x00100010) == 0x00100010) {
		printf("%s : error code = 0x%08x\n", __FUNCTION__, (int)status);
		return 1;
	}

	/* back to read-array */
	*addr = 0x00ff00ff;
	return 0;
}

static int do_write_buffer_32bit(struct strata_flash_descriptor *chip, u32 dest, u8 *mem, u32 len)
{
	int cache_size, i;
	u32 count,status;
	u32 *data;
	volatile u32 *blkadr, *start;

	if (!chip) return -1;

	/* Buffer write */
	cache_size = chip->cache_size;

	blkadr = (volatile u32*) dest;
	start = blkadr;
	data = (u32*) mem;

	while( len > 0 ) {

		if( len > cache_size) count = cache_size / 4;
		else count = len / 4;

		/* is buffer avaible? */
		do {
			/* issue write buffer command to block address */
			*blkadr = 0x00e800e8;

			/* read extended status register at block address */
			status = *blkadr;

		}while( (status & 0x00800080) != 0x00800080);


		/* write word count - block address */
		*blkadr = ((count-1) <<16) | (count-1);

		/* write buffer data - start address */
		for(i=0; i<count; i++) {
			*start++ = *data++;
		}

		/* write confirm 0xD0 to block address */
		*blkadr = 0x00d000d0;

		do{
			status = *blkadr;
		}while( (status & 0x00800080) != 0x00800080 );

		/* program error? */
		if( (status & 0x00100010) == 0x00100010) {
			printf("%s : error code = 0x%08x\n", __FUNCTION__, (int)status);
			return 1;
		}

		len  -= (count << 2);

	}

	/* read-array mode */
	*blkadr = 0x00ff00ff;

	return 0;
}

static int do_flash_write_32bit(struct strata_flash_descriptor *chip, u32 dest, u8 *mem, u32 len)
{
	int i, ret;
	u32 *data;
	u32 blkadr, writelen, left;
	struct strata_flash_region *r;

	if (!chip) return -1;

	/* Erase first */
	ret = do_flash_erase_32bit(chip, dest, len);
	if (ret) return ret;

	printf("Flashing 0x%08x bytes at 0x%08x:", (int)len, (int)dest);

	blkadr = dest;
	left = len;
	data = (u32*) mem;

	while ( blkadr < (dest + len) ) {
		r = REGION_SEL(blkadr, chip);
		if (!r) return -1;
		
		writelen = (left > r->block_size) ? r->block_size : left;

		if (chip->cache_size) 
			ret = do_write_buffer_32bit(chip, blkadr, (u8*)data, writelen);
		else {
			for (i=0; i<writelen/4; i++) {
				ret = do_write_word_32bit(chip, blkadr + i*4, data[i]);
				if (ret) break;
			}
		}
		if (ret) return ret;

		data += writelen/4;
		blkadr += r->block_size;
		left -= writelen;

		printf(".");
	}

	printf("done\n");

	return 0;
}

static int unlock_block_16bit(u32 addr)
{
	volatile u16 *blkadr= (volatile u16*)addr;
	u16 status;

	/* clear status */
	*blkadr = 0x0050;

	*blkadr = 0x0060;
	*blkadr = 0x00d0;

	do {
		status = *blkadr;
	}while( (status & 0x0080) != 0x0080);

	
	if( status != 0x0080) {
		printf("%s: error code = 0x%04x\n", __FUNCTION__, status);
		return 1;
	}

	*blkadr = 0x00ff;

	return 0;
}

static int erase_block_16bit(u32 addr)
{
	volatile u16 *blkadr = (volatile u16*) addr;
	u16 status;

	/* clear status */
	*blkadr = 0x0050;

	*blkadr = 0x0020;
	*blkadr = 0x00d0;

	/* BUSY? */
	do {
		status = *blkadr;
	}while( (status & 0x0080) != 0x0080);

	/* erase error? */
	if( (status & 0x0020) == 0x0020) {
		printf("%s: error code = 0x%04x\n", __FUNCTION__, status);
		return 1;
	}

	*blkadr = 0x00ff;

	return 0;
}

static int do_flash_erase_16bit(struct strata_flash_descriptor *chip, u32 dest, u32 len)
{
	int blocks, ret;
	u32 blkadr;
	struct strata_flash_region *r;

	if (!chip) return -1;

	/* unlocking */
	blocks  = 0;
	blkadr  = dest;
	for(;;) {
		/* J3 : unlock one for all, K3/K18/L3/L18 : unlock for each block*/
		if (chip->type == STRATA_J3) {
			if (!blocks) unlock_block_16bit(blkadr);
		}
	       	else
			unlock_block_16bit(blkadr);

		r = REGION_SEL(blkadr, chip);
		if (!r)  return -1;

		blkadr += r->block_size;
		blocks ++;

		if (blkadr >= (dest+len)) break;
	}

	printf("Erasing %d blocks at 0x%08x:", blocks, (int)dest);

	/* erasing */
	blkadr = dest;
	for(;;) {
		ret = erase_block_16bit(blkadr);
		if (ret) return ret;
		
		r = REGION_SEL(blkadr, chip);
		if (!r) return -1;

		blkadr += r->block_size;
		printf(".");

		if (blkadr >= (dest+len)) break;
	}

	printf("done\n");

	return 0;
}

static int do_write_word_16bit(struct strata_flash_descriptor *chip, u32 dest, u16 data) 
{
	volatile u16 *addr = (volatile u16*) dest;
	u16 status;

	if (!chip) return -1;

	/* clear status */
	*addr = 0x0050;

	/* program setup */
	*addr= 0x0040;

	/* data */
	*addr = data;

	/* read status */
	*addr = 0x0070;

	do{
		status = *addr;
	}while( (status & 0x0080) != 0x0080);

	/* program error? */
	if( (status & 0x0010) == 0x0010) {
		printf("%s : error code = 0x%08x\n", __FUNCTION__, (int)status);
		return 1;
	}

	*addr = 0x00ff;

	return 0;
}

static int do_write_buffer_16bit(struct strata_flash_descriptor *chip, u32 dest, u8* mem, u32 len)
{
	int cache_size, i;
	u32 count;
	u16 status;
	u16 *data;
	volatile u16 *blkadr, *start;

	if (!chip) return -1;

	/* Buffer write */
	cache_size = chip->cache_size;

	blkadr = (volatile u16*) dest;
	start = blkadr;
	data = (u16*) mem;

	while( len > 0 ) {

		if( len > cache_size) count = cache_size / 2;
		else count = len / 2;

		/* is buffer avaible? */
		do {
			/* issue write buffer command to block address */
			*blkadr = 0x00e8;

			/* read extended status register at block address */
			status = *blkadr;

		}while( (status & 0x0080) != 0x0080);


		/* write word count - block address */
		*blkadr = (count-1);

		/* write buffer data - start address */
		for(i=0; i<count; i++) {
			*start++ = *data++;
		}

		/* write confirm 0xD0 to block address */
		*blkadr = 0x00d0;

		do{
			status = *blkadr;
		}while( (status & 0x0080) != 0x0080);

		/* program error? */
		if( (status & 0x0010) == 0x0010) {
			printf("%s : error code = 0x%04x\n", __FUNCTION__, status);
			return 1;
		}

		len  -= (count << 1);
	}

	/* read-array mode */
	*blkadr = 0x00ff;

	return 0;
}

static int do_flash_write_16bit(struct strata_flash_descriptor *chip, u32 dest, u8 *mem, u32 len)
{
	int  i,ret;
	u16 *data;
	u32 blkadr, left, writelen;
	struct strata_flash_region *r;

	if (!chip) return -1;

	/* Erase first */
	ret = do_flash_erase_16bit(chip, dest, len);
	if (ret) return ret;

	printf("Flashing 0x%08x bytes at 0x%08x:", (int)len, (int)dest);

	blkadr = dest;
	left = len;
	data = (u16*) mem;

	while ( blkadr < (dest + len) ) {
		r = REGION_SEL(blkadr, chip);
		if (!r) return -1;
		
		writelen = (left > r->block_size) ? r->block_size : left;

		if (chip->cache_size) 
			ret = do_write_buffer_16bit(chip, blkadr, (u8*)data, writelen);
		else {
			for (i=0; i<writelen/2; i++) {
				ret = do_write_word_16bit(chip, blkadr + i*2, data[i]);
				if (ret) break;
			}
		}
		if (ret) return ret;

		data += writelen/2;
		blkadr += r->block_size;
		left -= writelen;

		printf(".");
	}

	printf("done\n");

	return 0;
}

static int do_flash_probe_16bit(struct strata_flash_descriptor *chip)
{
	volatile u16 *addr;
	u16 code, id;

	if (!chip) return -1;

	addr = (volatile u16 *)chip->base;

	*addr = 0x0098;
	code = *addr;
	id = *(addr+1);
	*addr = 0x00ff;

	chip->type = STRATA_UNKNOWN;
	chip->id = id;

	if(code == 0x89) {
		int i,j; 
		struct strata_flash_descriptor *c;

		for (i=0; i< (sizeof(chips_table)/sizeof(struct strata_flash_descriptor)); i++) {
			c = chips_table + i;
			if ( id == c->id) {
				chip->type = c->type;
				chip->cache_size = c->cache_size * 2;
				chip->region_num = c->region_num;

				for (j=0; j < c->region_num; j++) {
					chip->regions[j].offset = c->regions[j].offset * 2;
					chip->regions[j].block_num = c->regions[j].block_num;
					chip->regions[j].block_size = c->regions[j].block_size * 2;
				}

				break;
			}
		}
	}

	return 0;
}

/* program flash in blocks */
int flash_write(struct strata_flash_descriptor *chip, u32 dest, u8 *mem, u32 len)
{
	int err=0;
	struct strata_flash_region *r;

	if (!chip) return -1;

	/* sanity check */
	if (chip->type == STRATA_UNKNOWN) {
		printf("%s : unknown flash type %d { id=%08x }\n", __FUNCTION__, (int)chip->type,
				(int)chip->id);
		return -1;
	}

	r = REGION_SEL(dest, chip);
	if (!r) return -1;

	if (dest%r->block_size) {
		printf("%s : Not align to region->block_size:0x%08x\n", __FUNCTION__,
				(int)r->block_size);
		return -1;
	}

	switch(chip->buswidth) {
	case 2:
		err = do_flash_write_16bit(chip, dest, mem, len);
		break;
	case 4:
		err = do_flash_write_32bit(chip, dest, mem, len);
		break;
	default:
		err = -1;
		printf("flash_write: buswidth = %d is not supported\n", (int)chip->buswidth);
		break;
	}

	return err;
}

/* erase flash in blocks */
int flash_erase(struct strata_flash_descriptor *chip, u32 dest, u32 len)
{
	int err=0;
	struct strata_flash_region *r;

	if (!chip) return -1;

	/* sanity check */
	if (chip->type == STRATA_UNKNOWN) {
		printf("%s : unknown flash type %d { id=%08x }\n", __FUNCTION__, (int)chip->type,
				(int)chip->id);
		return -1;
	}

	r = REGION_SEL(dest, chip);
	if (!r) return -1;

	if (dest%r->block_size) {
		printf("%s : Not align to region->block_size:0x%08x\n", __FUNCTION__,
				(int)r->block_size);
		return -1;
	}

	switch(chip->buswidth) {
	case 2:
		err = do_flash_erase_16bit(chip, dest, len);
		break;
	case 4:
		err = do_flash_erase_32bit(chip, dest, len);
		break;
	default:
		err = -1;
		printf("flash_write: buswidth = %d is not supported\n", (int)chip->buswidth);
		break;
	}

	return err;

}

int flash_probe(struct strata_flash_descriptor *chip)
{
	int err=0;

	if (!chip) return -1;

	switch(chip->buswidth) {
	case 2:
		err = do_flash_probe_16bit(chip);
		break;
	case 4:
		err = do_flash_probe_32bit(chip);
		break;
	default:
		err = -1;
		printf("flash_write: buswidth = %d is not supported\n", (int)chip->buswidth);
		break;
	}

	return err;
}
